using System;
using System.Linq.Expressions;
using Legend.Fakes.Configuration;
using Legend.Fakes.Api;

namespace Legend.Fakes.Configuration
{
    internal class FakeConfiguration<TFake>
            : IFakeConfiguration<TFake>,
              IVoidConfiguration<TFake>,
              IRepeatConfiguration<TFake>,
              IExpectationConfiguration<TFake>,
              IAfterCallSpecifiedConfiguration<TFake> where TFake : class
    {
        internal FakeConfiguration(FakeObject fake)
        {
            this.Fake = fake;
        }

        public FakeObject Fake
        {
            get; private set;
        }

        public BuildableCallRule RuleBeingBuilt { get; private set; }

        public IReturnValueConfiguration<TFake, TMember> CallsTo<TMember>(Expression<Func<TFake, TMember>> callSpecification)
        {
            this.InitializeInterceptorBeingBuilt(callSpecification);

            return new ReturnValueConfiguration<TMember> { ParentConfiguration = this };
        }

        public IVoidConfiguration<TFake> CallsTo(Expression<Action<TFake>> callSpecification)
        {
            this.InitializeInterceptorBeingBuilt(callSpecification);
            this.RuleBeingBuilt.Applicator = x => { };

            return this;
        }

        private void InitializeInterceptorBeingBuilt(LambdaExpression expression)
        {
            var callRule = new ExpressionCallRule(expression);
            Fake.AddRule(callRule);
            this.RuleBeingBuilt = callRule;
        }

        public IAfterCallSpecifiedConfiguration<TFake> Throws(Exception exception)
        {
            this.RuleBeingBuilt.Applicator = x => { throw exception; };
            return this;
        }

        public void NumberOfTimes(int numberOfTimes)
        {
            Guard.IsInRange(numberOfTimes, 1, int.MaxValue, "numberOfTimes");

            this.RuleBeingBuilt.NumberOfTimesToCall = numberOfTimes;
        }

        public IAfterCallSpecifiedConfiguration<TFake> Event(Expression<Action<TFake>> eventRegistration)
        {
            throw new NotImplementedException();
        }

        public class ReturnValueConfiguration<TMember>
            : IReturnValueConfiguration<TFake, TMember>
        {
            public FakeConfiguration<TFake> ParentConfiguration;

            public IAfterCallSpecifiedConfiguration<TFake> Returns(TMember value)
            {
                this.ParentConfiguration.RuleBeingBuilt.Applicator = x => x.SetReturnValue(value);
                return this.ParentConfiguration;
            }

            public IAfterCallSpecifiedConfiguration<TFake> Returns(Func<TMember> valueProducer)
            {
                Guard.IsNotNull(valueProducer, "valueProducer");

                this.ParentConfiguration.RuleBeingBuilt.Applicator = x => x.SetReturnValue(valueProducer.Invoke());
                return this.ParentConfiguration;
            }

            public IAfterCallSpecifiedConfiguration<TFake> Throws(Exception exception)
            {
                return this.ParentConfiguration.Throws(exception);
            }

            public void MustNotHappen()
            {
                this.ParentConfiguration.MustNotHappen();
            }

            public IRepeatConfiguration<TFake> MustHappen()
            {
                return this.ParentConfiguration.MustHappen();
            }
        }

        public void MustNotHappen()
        {
            this.RuleBeingBuilt.NumberOfTimesToCall = 0;
            this.RuleBeingBuilt.MayNotBeCalledMoreThanTheNumberOfTimesSpecified = true;
        }

        public IRepeatConfiguration<TFake> MustHappen()
        {
            this.RuleBeingBuilt.NumberOfTimesToCall = 1;
            this.RuleBeingBuilt.MustGetCalled = true;
            return this;
        }


        public IVoidConfiguration<TFake> AnyCall()
        {
            this.RuleBeingBuilt = new AnyCallCallRule();
            this.Fake.AddRule(this.RuleBeingBuilt);

            return this;
        }

        private class AnyCallCallRule
            : BuildableCallRule
        {
            protected override bool OnIsApplicableTo(IFakeObjectCall call)
            {
                return true;
            }

            public override void IgnoreArguments()
            {
                
            }
        }
    }
}
